/*
* main.c - openwii umod2, elegantly simple version
* Copyright 2006-2008 OpenWii (admin@openwii.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* Version 2.4 by Andii
*/

#include "common.h"
#include "drive.h"

// prototype the main function as noreturn
int main() __attribute__ ((noreturn));

// LED related stuff
static uint8_t led_off_cnt;
#define LED_TIME_BASE (5 * 3)	// ~ms consumed by SerialRequest * 3 calls
#define LED_ON_TIME 300
#define LED_CYCLE 800

static void LifeLED(uint8_t off_cnt)
{
	static uint16_t	led_cnt;
	static uint8_t	led_state;

	if (off_cnt != 0)
	{
		LEDON;
		led_state = 1;
		led_cnt = 2000;
		if (led_off_cnt < off_cnt)
		{
			led_off_cnt = off_cnt;
		}
	}
	else if (led_off_cnt != 0)
	{
		if (led_cnt >= LED_TIME_BASE)
		{
			led_cnt -= LED_TIME_BASE;
		}
		else
		{
			if (led_state == 0)
			{
				LEDON;
				led_cnt = LED_ON_TIME;
			}
			else
			{
				LEDOFF;
				led_cnt = LED_CYCLE - LED_ON_TIME;
				led_off_cnt--;
			}
			led_state ^= 1;
		}
	}
}

// teh main!
int main()
{
	uint8_t region;
	uint8_t speed;

	uint16_t unlockaddr;
	uint16_t orgret;

	machInit();
	led_off_cnt = 0;
	LifeLED(2);

	Sync();

	// detect drive
	SerialRequest(CMD_READ8, DRIVE_TYPE, 0);
	{
		const uint8_t* drive_code;
		if (ret_word == 0x20)
		{	// DMS/D2A
			unlockaddr = 0x82b6;
			orgret = 0xd4c7;
			drive_code =&BINARYSTARTDMS;
		}
		else
		{	// D2B
			unlockaddr = 0x82be;
			orgret = 0xd54a;
			drive_code =&BINARYSTARTD2B;
		}
		SerialRequest(CMD_WRITE8, unlockaddr, 0x41);
		// upload our mn10200 code to drive
		SendBlock(DRIVE_CODE, drive_code, (uint16_t)&BINARYSIZEDMS);
	}

	region = nvReadByte(EEP_REGION);
	speed = nvReadByte(EEP_SPEED);
	if (speed > (CONFIG_MAX_SPEED + AFX_SWITCH))
	{	// if eeprom speed (audiofix) data is not configured
		speed = CONFIG_MAX_SPEED;	// setup max speed and audiofix on
		goto write_speed;			// write back to eeprom
	}
	goto upload_config;

	while (42)
	{
		LifeLED(0);

		// unlock debug mode
		SerialRequest(CMD_WRITE8, unlockaddr, 0x41);
		// check to see if its time to jack in our code
		if (SerialRequest(CMD_READ16, STACK_RET, 0))
		{
			if (ret_word == orgret)
			{
				// replace the return address from discLogin with the drive code address
				SerialRequest(CMD_WRITE16, STACK_RET, (uint16_t)DRIVE_CODE);
				SerialRequest(CMD_WRITE8, STACK_RET + 2, DRIVE_CODE >> 16);
				LifeLED(3);
			}
		}
		else
		{
			Sync();
		}

		// check setup disc configuration changes
		if (SerialRequest(CMD_READ16, CONFIG_XFER, 0))
		{
			if ((ret_word >> 8) == CONFIG_MAGIC)
			{	// change region / speed /audiofix
				uint8_t command = ret_word;
				switch (command)
				{
					case CONFIG_JAP_R:
					case CONFIG_USA_R:
					case CONFIG_PAL_R:
					case CONFIG_res_R:
					case CONFIG_OFF_R:
						region = command;
					break;

					case CONFIG_SLOW_SPEED:
					case CONFIG_MAX_SPEED:
						speed = command | (speed & AFX_SWITCH);
					break;

					case CONFIG_AFX_ON:
						speed &= (~AFX_SWITCH);
					break;

					case CONFIG_AFX_OFF:
						speed |= AFX_SWITCH;
					break;

					case CONFIG_UPDATE:
						asm volatile
						(	// call the bootloader
							"ldi r30, %0\n\t"
							"ldi r31, %1\n\t"
							"ijmp\n\t"
							:: "M" ((uint8_t)(BOOTLADDR / 2)), "M" ((BOOTLADDR / 2) >> 8)
						);
					break;
				}
				LifeLED(4);
				nvWriteByte(EEP_REGION, region);
write_speed:	nvWriteByte(EEP_SPEED, speed);
upload_config:	SerialRequest(CMD_WRITE16, REGION_XFER, ((uint16_t)speed << 8) | region);
				SerialRequest(CMD_WRITE16, CONFIG_XFER, 0);		// reset configuration command
			}
		}
	}
}
